using System;
using Lucene.Net.Index;
using Lucene.Net.Documents;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Store;
using log4net;
using MShop.Core.Util;
 
 

namespace MShop.Core.Service.Search
{
	/// <summary>
	/// The IndexBuilder class takes care of maintaining the fulltext index.
	/// </summary>
	public class IndexBuilder : IDisposable
	{
		private static readonly ILog log = LogManager.GetLogger(typeof(IndexBuilder));

		private readonly ITextExtractor _textExtractor;
		private readonly Directory _indexDirectory;
		private IndexWriter _indexWriter;
		private bool _isClosed = false;
		private readonly bool _rebuildIndex;
        private readonly Lucene.Net.Analysis.Analyzer _analyzer;
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="physicalIndexDir">Location of the index files.</param>
		/// <param name="rebuildIndex">Flag to indicate if the index should be rebuilt. 
		/// <param name="textExtractor">The text extractor that can be used to extract text from content.</param>
		public IndexBuilder(string physicalIndexDir, bool rebuildIndex, ITextExtractor textExtractor)
		{
            this._indexDirectory = FSDirectory.GetDirectory(physicalIndexDir);
			this._rebuildIndex = rebuildIndex;
			this._textExtractor = textExtractor;
            _analyzer = new Lucene.Net.Analysis.PanGu.PanGuAnalyzer();
           // _analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer();
			InitIndexWriter();

			log.Info("IndexBuilder created.");
		}

		/// <summary>
		/// Initialize the IndexWriter depending on the rebuildIndex flag
		/// </summary>
		private void InitIndexWriter()
		{
			if (!IndexReader.IndexExists(this._indexDirectory) || this._rebuildIndex)
			{
                this._indexWriter = new IndexWriter(_indexDirectory, _analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
			}
			else
			{
                this._indexWriter = new IndexWriter(this._indexDirectory, _analyzer, false, IndexWriter.MaxFieldLength.UNLIMITED);
              
			}
		}

		/// <summary>
		/// Add content to be indexed.
		/// </summary>
		/// <param name="searchContent"></param>
		public void AddContent(ISearchableContent searchContent)
		{
			if (this._indexWriter == null)
			{
				InitIndexWriter();
			}
            this._indexWriter.AddDocument(BuildDocumentFromSearchContent(searchContent,this._textExtractor));
		}
		

		/// <summary>
		/// Update existing content in the search index.
		/// </summary>
		/// <param name="searchContent"></param>
        public void UpdateContent(ISearchableContent searchContent)
		{
			if (this._rebuildIndex)
			{
				throw new InvalidOperationException("Cannot update documents when rebuilding the index.");
			}
			else
			{
				// First delete the old content
				DeleteContent(searchContent);
				// Now add the content again
				AddContent(searchContent);
			}
		}

		

		/// <summary>
		/// Delete existing content from the search index.
		/// </summary>
		/// <param name="searchContent"></param>
        public void DeleteContent(ISearchableContent searchContent)
		{
			if (this._rebuildIndex)
			{
				throw new InvalidOperationException("Cannot delete documents when rebuilding the index.");
			}
			else
			{
				this._indexWriter.Close();
				this._indexWriter = null;

				// The path uniquely identifies a document in the index.
				Term term = new Term("path", searchContent.ToSearchContent().Path);
				IndexReader rdr = IndexReader.Open(this._indexDirectory,false);
				rdr.DeleteDocuments(term);
				rdr.Close();
			}
		}

		
		/// <summary>
		/// Close the IndexWriter (saves the index).
		/// </summary>
		public void Close()
		{
			if (!this._isClosed && this._indexWriter != null)
			{
				//don't do this everytime 
				this._indexWriter.Optimize();
				this._indexWriter.Close();
				this._isClosed = true;
				log.Info("New or updated search index written to disk.");
			}
		}


        private Document BuildDocumentFromSearchContent(ISearchableContent searchableContent, ITextExtractor textExtractor)
        {
            if (searchableContent == null) throw new ArgumentNullException("searchableContent can not be null");

            // Get the text of the content item to index
            string contentToIndex = searchableContent.ToSearchContent(textExtractor);
            // strip (x)html tags
            string plainTextContent = System.Text.RegularExpressions.Regex.Replace(contentToIndex, @"<(.|\n)*?>", string.Empty);

            SearchContent searchContent= searchableContent.ToSearchContent();

            // create the actual url
            string path = searchContent.Path;
            // check that summary is not null
           
            Document doc = new Document();
            string summary = string.IsNullOrEmpty(searchContent.Summary)? Text.TruncateText(plainTextContent, 200):searchContent.Summary;
            doc.Add(new Field("title", searchContent.Title, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("summary", summary, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("contents", plainTextContent, Field.Store.NO, Field.Index.ANALYZED));          
            doc.Add(new Field("author", searchContent.Author, Field.Store.YES, Field.Index.ANALYZED));
            doc.Add(new Field("path", path, Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("category", searchContent.Category, Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("id", searchContent.Id, Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("datecreated", Lucene.Net.Documents.DateTools.DateToString(searchContent.DateCreated, DateTools.Resolution.DAY), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.Add(new Field("datemodified", Lucene.Net.Documents.DateTools.DateToString(searchContent.DateModified, DateTools.Resolution.DAY), Field.Store.YES, Field.Index.NOT_ANALYZED));
            doc.SetBoost(searchContent.Boost);
            
            foreach (CustomSearchField field in searchableContent.GetCustomSearchFields())
            {
                Field.Store store = field.IsStored ? Field.Store.YES : Field.Store.NO;
                Field.Index index = field.IsTokenized ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
                if (field.FieldKey != null && field.FieldValue != null)
                {
                    doc.Add(new Field(field.FieldKey, field.FieldValue, store, index));
                }
            }
            return doc;
        }

		 

		#region IDisposable Members

		public void Dispose()
		{
			Close();
		}

		#endregion
	}
}
